#if (BECLEAR_SMARTTV == 1) || (BECLEAR_SMARTHOME == 1)
#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <xclib.h>
#include <stdint.h>
#include "userbuffermanagement.h"
#include "devicedefines.h"
#include "beclear_int.h"
#include "keyword.h"
#include "src.h"
#if ( BECLEAR_SMARTTV == 1 )
#include "beclear_srtvbf.h"
#else
#include "beclear_ambiphone.h"
#endif
#include <print.h>

#include "APESinclude.h"
#if BECLEAR_SMARTHOME
#include "beclear_application_smarthome.h"
#include "ambiphone_xmos.h"
#else /* BECLEAR_SMARTTV */
#include "beclear_application_smarttv.h"
#include "srtvbf_xmos.h"
#endif

#if ( BECLEAR_VAD == 1)
/* User call back for VAD */
void vad_set_status(unsigned);

/* Objects on BAP tile */
unsafe 
{
    extern BAP_APPL_struct * unsafe bap_ap;
    extern OBJStruct bap_obj0;
    static OBJStruct * unsafe bap_op = &(bap_obj0);
}
#endif

#if (SENSORY_KEYWORD == 1)
void keyword_set_status(unsigned);
#endif

unsafe
{
    /* Global input buffers for lib_beclear */
    extern APES_FLOAT              * unsafe * unsafe mics1;
    extern APES_FLOAT              * unsafe * unsafe far1;
    /* Global output buffer for lib_beclear */
    extern APES_FLOAT * unsafe q_a[3][ NOUT];
}

/* This runs on AEC tile */
[[combinable]]
void BeclearBuff(server audManage_if i_audMan, client beclear_if i_beclear)
{
    unsigned sampleCount = 0;

    while(1)
    {
        select
        {
            /* TODO consider re-naming these to near/far.. */
            case i_audMan.transfer_samples(int in_mic_buf[], int in_spk_buf[], int out_mic_buf[], int out_spk_buf[]):

                /* Add samples to mic and far buffers */
                for(int i = 0; i < BECLEAR_NMICS; i++)
                unsafe{
                    mics1[i][sampleCount] = in_mic_buf[i];
                }

                for(int i = 0; i < BECLEAR_NFAR; i++)
                unsafe{
                    far1[i][sampleCount] = in_spk_buf[i];
                }

                sampleCount++;

                if(sampleCount >= BECLEAR_BLOCKSIZE_SAMPLES)
                unsafe{
                    i_beclear.swap_buffers();
                    sampleCount = 0;
                }
                break;
        }
    }
}

static union usbAecDS3
{
    long long doubleWordAlignmentEnsured;
    int32_t delayLine[BECLEAR_NFAR][SRC_FF3V_FIR_NUM_PHASES][SRC_FF3V_FIR_TAPS_PER_PHASE];
} usbAecDS3;
static int64_t usbAecDs3Sum[BECLEAR_NFAR];

static union asrUS3
{
    long long doubleWordAlignmentEnsured;
    int32_t delayLine[SRC_FF3V_FIR_TAPS_PER_PHASE];
} asrUS3;

static union commsUS3
{
    long long doubleWordAlignmentEnsured;
    int32_t delayLine[SRC_FF3V_FIR_TAPS_PER_PHASE];
} commsUS3;

static int usbToBeclearRatioCounter = 0;

//typedef enum
//{
  //  KW_WAIT,
   // KW_DETECT,
    //KW_END
//}
//kwstate_t;

void UserBufferManagementInit(client audManage_if i_audMan)
{
    usbToBeclearRatioCounter = 0;

    if (USB_TO_BECLEAR_RATIO > 1)
    {
        memset(usbAecDS3.delayLine, 0, sizeof usbAecDS3.delayLine);
        memset(&usbAecDs3Sum, 0, sizeof usbAecDs3Sum);
        memset(commsUS3.delayLine, 0, sizeof commsUS3.delayLine);
        memset(asrUS3.delayLine, 0, sizeof asrUS3.delayLine);
    }
}

/* sampsFromUsbToAudio: The sample frame the device has received from the host and is going to play to the output audio interfaces
 * sampsFromAudioToUsb: The sample frame that was received from the audio interfaces and that the device is going to send to the host
 *
 * Note: this is called from audio_io()
 */
#pragma unsafe arrays
void UserBufferManagement(unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[], client audManage_if i_audMan)
{
    int inputNear[BECLEAR_NMICS]; // Mic samples
    int inputFar[BECLEAR_NFAR];   // AEC reference samples
    int commOutput = 0;
    int asrOutput = 0;

    static int outSampCount = 0;
    static int readBuffNo = 0;
#if( TEST_MODE == 1 )
    static int sendDataToBeclear = 0;
#endif

#if (SENSORY_KEYWORD == 1)
    static int kwresult = 0;
#endif

    /* 48kHz or 16kHz (matches Audio core rate) */
    // Prep AEC ref samples, e.g. mix down and add to inputFar buffer
    if (BECLEAR_NFAR > 1)
    {
        for (int i = 0; i < BECLEAR_NFAR; ++i)
        {
            if (BECLEAR_AECREF_I2S)
            {
                inputFar[i] = (sampsFromAudioToUsb[i]); /* Take far data from I2S */
            }
            else
            {
                inputFar[i] = (sampsFromUsbToAudio[i]); /* Take far data from USB */
            }
        }
    }
    else if (BECLEAR_AECREF_I2S)
    {
        // Use right channel from I2S
        inputFar[0] = (sampsFromAudioToUsb[1]);
    }
    else
    {
        // Simple stereo -> mono mix of USB audio from host as far
        inputFar[0] = (((int)sampsFromUsbToAudio[0])>>1) + (((int)sampsFromUsbToAudio[1])>>1);
    }

    // Write AEC reference to I2S output buffer before it gets down-sampled
    if (NUM_USB_CHAN_OUT > 0)
    {
        // TODO: add > mono AEC support
        // AEC ref on right channel
        sampsFromUsbToAudio[1] = inputFar[0];
    }

#if( TEST_MODE == 1 )
    // Wait for non-zero sample on i2s before sending anything to BeClear
    if (!sendDataToBeclear && inputFar[0] != 0) {
        sendDataToBeclear = 1;
    }
#endif

    // Down-sample the AEC reference channels
    if (USB_TO_BECLEAR_RATIO > 1)
    {
        if ((USB_TO_BECLEAR_RATIO - 1) == usbToBeclearRatioCounter)
        {
            for (int i = 0; i < BECLEAR_NFAR; ++i)
            {
                inputFar[i] = src_ds3_voice_add_final_sample(usbAecDs3Sum[i],
                                                             usbAecDS3.delayLine[i][usbToBeclearRatioCounter],
                                                             src_ff3v_fir_coefs[usbToBeclearRatioCounter],
                                                             inputFar[i]);
            }
            memset(&usbAecDs3Sum, 0, sizeof usbAecDs3Sum);
        }
        else
        {
            for (int i = 0; i < BECLEAR_NFAR; ++i)
            {
                usbAecDs3Sum[i] = src_ds3_voice_add_sample(usbAecDs3Sum[i],
                                                           usbAecDS3.delayLine[i][usbToBeclearRatioCounter],
                                                           src_ff3v_fir_coefs[usbToBeclearRatioCounter],
                                                           inputFar[i]);
            }
        }
    }

    /* Always 16kHz */
    if ((USB_TO_BECLEAR_RATIO - 1) == usbToBeclearRatioCounter)
    {
        // Copy samples to local buffers
        // TODO: can we remove this copy, maybe use a slice i.e sampsFromAudioToUsb[PDM_MIC_INDEX:PDM_MIC_INDEX+BECLEAR_NMICS]?
        for(int i = 0; i < BECLEAR_NMICS; i++)
        {
            inputNear[i] = sampsFromAudioToUsb[PDM_MIC_INDEX+i] << 2;
        }

        // Send samples over to the DSP
        int outputNear[1]; // TODO: Remove, unused
        int outputFar[1];  // TODO: Remove, unused
#if( TEST_MODE == 1 )
        if (sendDataToBeclear) {
            i_audMan.transfer_samples(inputNear, inputFar, outputFar, outputNear);
        }
#else
        i_audMan.transfer_samples(inputNear, inputFar, outputFar, outputNear);
#endif

        // Copy BeClear output samples into locals
        unsafe
        {
            APES_FLOAT * unsafe * unsafe outq = q_a[readBuffNo];

            commOutput = (int) outq[0][outSampCount]; // Communications optimised output from BeClear
            if (BECLEAR_OUTPUT_ASR)
            {
                asrOutput = (int) outq[1][outSampCount]; // ASR optimised output from BeClear
            }
        }
    }

    /* 48kHz or 16kHz (matches Audio core rate) */
    // Up-sample BeClear output samples
    if (USB_TO_BECLEAR_RATIO > 1)
    {
        if ((USB_TO_BECLEAR_RATIO - 1) == usbToBeclearRatioCounter)
        {
            commOutput = src_us3_voice_input_sample(commsUS3.delayLine,
                                                    src_ff3v_fir_coefs[usbToBeclearRatioCounter],
                                                    commOutput);
            if (BECLEAR_OUTPUT_ASR)
            {
                asrOutput = src_us3_voice_input_sample(asrUS3.delayLine,
                                                       src_ff3v_fir_coefs[usbToBeclearRatioCounter],
                                                       asrOutput);
            }
        }
        else
        {
            size_t index = (USB_TO_BECLEAR_RATIO-1) - usbToBeclearRatioCounter - 1; // Walks backwards through fir coefs
            commOutput = src_us3_voice_get_next_sample(commsUS3.delayLine,
                                                       src_ff3v_fir_coefs[index]);
            if (BECLEAR_OUTPUT_ASR)
            {
                asrOutput = src_us3_voice_get_next_sample(asrUS3.delayLine,
                                                          src_ff3v_fir_coefs[index]);
            }
        }
    }

    // Write to I2S output buffer
    if (NUM_USB_CHAN_OUT > 0)
    {
        #ifdef NO_USB
        {
            // NO_USB means an I2S only device, so BeClear comms output on left channel
            sampsFromUsbToAudio[0] = commOutput;
        }
        #else
        {
            // For a UAC device, mono output (on right channel), so zero left
            sampsFromUsbToAudio[0] = 0;
        }
        #endif
    }

#if (SENSORY_KEYWORD_MUTE == 1)
    if(!kwresult)
        asrOutput = 0;
#endif

    // Write to USB output buffer
    if (NUM_USB_CHAN_IN > 0)
    {
        switch (NUM_USB_CHAN_IN)
        {
            case 2:
                /*
                 * ch[0]:                  Comms output
                 * ch[1]:                  ASR output
                 */
                sampsFromAudioToUsb[0] = commOutput;
                // TODO if(BECLEAR_OUTPUT_ASR)
                sampsFromAudioToUsb[1] = asrOutput;
                break;

            case 6:
                /*
                 * ch[0]:                  Comms or ASR output
                 */
                if (BECLEAR_OUTPUT_ASR)
                {
                    sampsFromAudioToUsb[0] = asrOutput;
                }
                else
                {
                    sampsFromAudioToUsb[0] = commOutput;
                }
                /*
                 * ch[1...BECLEAR_NMICS]:  Raw mics
                 * ch[1 + BECLEAR_NMICS]:  AEC ref
                 *
                 * Note: input samples (near and far) will now be @ 16kHz if USB_TO_BECLEAR_RATIO > 1
                 */
                for (int i = 0; i < BECLEAR_NMICS; ++i)
                {
                    sampsFromAudioToUsb[1+i] = inputNear[i];
                }

                sampsFromAudioToUsb[1+BECLEAR_NMICS] = inputFar[0];
                break;

             case 1:
             default:
                /* ch[0]:                  Comms or ASR output */
                if (BECLEAR_OUTPUT_ASR)
                {
                    sampsFromAudioToUsb[0] = asrOutput;
                }
                else
                {
                    sampsFromAudioToUsb[0] = commOutput;
                }
                break;
        }
    }

    /* Always 16kHz */
    if ((USB_TO_BECLEAR_RATIO - 1) == usbToBeclearRatioCounter)
    {
        usbToBeclearRatioCounter = 0;

#if( TEST_MODE == 1 )
        if (sendDataToBeclear) {
            ++outSampCount;
        }
#else
        ++outSampCount;
#endif
        if (outSampCount == BECLEAR_BLOCKSIZE_SAMPLES)
        unsafe{
            outSampCount = 0;
#if BECLEAR_VAD
            int voiceActive;
            PARAMpv params[1];

            params[0].size = 1;
            params[0].valptr = &voiceActive;
#if BECLEAR_SMARTTV
            params[0].param = SRTVBF_VOICEACTIVITY;
            SRTVBF_BAP_GetPar(bap_op, params, 1, bap_ap->srtvbf_bap);
#else /* BECLEAR_SMARTHOME */
            params[0].param = AMBIPHONE_VOICEACTIVITY;
            AMBIPHONE_BAP_GetPar(bap_op, params, 1, bap_ap->ambiphone_bap);
#endif
            /* Make call to user implemented callback for VAD functionality */
            vad_set_status(voiceActive);
#endif

#if (SENSORY_KEYWORD == 1)
            /* Always use ASR optimised output from BeClear for keyword */
            kwresult = keyword_buffer_ready((uint32_t) q_a[readBuffNo][1]); 
            
            /* Make a call to user implemented callback for KW functionality */
            keyword_set_status(kwresult);

#endif
            /* Move the BeClear output buffer on */
            ++readBuffNo;
            if (readBuffNo == 3)
            {
                readBuffNo = 0;
            }
        }
    }
    else
    {
        ++usbToBeclearRatioCounter;
    }
}
#endif /* BECLEAR_SMARTHOME || BECLEAR_SMARTTV */
